package com.atguigu.gateway.api.filter;

import com.atguigu.gateway.api.config.AuthJwtProperties;
import com.atguigu.gmall.common.utils.IpUtils;
import com.atguigu.gmall.common.utils.JwtUtils;
import com.atguigu.gmall.common.utils.RsaUtils;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
    //因为解析路由配置给当前过滤器的参数列表由当前类的父类来解析，所以必须将类型传递给父类
    public AuthGatewayFilterFactory() {
        super(Config.class);
    }
    @Autowired
    AuthJwtProperties jwtProperties;
    @Override
    public ShortcutType shortcutType() {
        //纯字符串列表，多个字符串使用逗号分隔
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");//指定Config中接收参数列表的成员属性
    }

    //提供一个过滤器对象：用来过滤请求
    @Override
    public GatewayFilter apply(Config config) {//参数：config 代表路由配置给当前过滤器对象的参数列表
        return (ServerWebExchange exchange, GatewayFilterChain chain)->{
            ServerHttpRequest request = exchange.getRequest();
            //1、获取请求路径 判断 是否需要验证登录状态
            //获取(http://www.atguigu.com/toLogin.html)   getPath获取前面URI中的 /toLogin.html
            String url = request.getURI().getPath();
            // /*  使用Ant风格的比较器判断
//            AntPathMatcher matcher = new AntPathMatcher();
//            matcher.match("/*.html" , "/1.html");
            boolean allMatch = config.getPaths().stream().allMatch(path -> path.indexOf(url) == -1);
            if(allMatch){
                //2、无需验证：直接放行
                return chain.filter(exchange);
            }
            //3、需要验证：
            //4、 获取请求报文中的cookie中的GMALL-TOKEN:
            //前端js代码可能会将GMALL-TOKEN获取到之后以localstorage的方式存储下次访问时会用请求头:token来提交
            String token = request.getHeaders().getFirst(jwtProperties.getHeaderName());
            if(StringUtils.isEmpty(token)){
             //请求头中没有 尝试从cookie中获取
                MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                if(!CollectionUtils.isEmpty(cookies) && cookies.containsKey(jwtProperties.getTokenName())){
                    token = cookies.getFirst(jwtProperties.getTokenName()).getValue();
                }
            }
            //如果token仍然为空，代表请求报文中没有携带token来访问
            //5、获取失败：跳转到登录页面
            ServerHttpResponse response = exchange.getResponse();
            if(StringUtils.isEmpty(token)){
                //响应一个重定向的报文 让浏览器访问登录页面
                response.setStatusCode(HttpStatus.SEE_OTHER);//设置重定向状态码
                response.getHeaders().setLocation(URI.create(jwtProperties.getRediectUrl()+
                        request.getURI()));// request.getURI() 当前请求访问的地址
                return response.setComplete();//完成响应报文的封装  直接响应
            }

            try {
                //6、获取成功：
                //7、解析token
                Map<String, Object> map = JwtUtils.getInfoFromToken(token,
                        RsaUtils.getPublicKey(jwtProperties.getPublicKeyPath()));
                //map中可以获取生成token时的客户端ip地址(判断token是否被盗用)
                String ip = map.get("ip").toString();
                if(!StringUtils.equals(ip , IpUtils.getIpAddressAtGateway(request))){
                    response.setStatusCode(HttpStatus.SEE_OTHER);//设置重定向状态码
                    response.getHeaders().setLocation(URI.create(jwtProperties.getRediectUrl()+
                            request.getURI()));// request.getURI() 当前请求访问的地址
                    return response.setComplete();//完成响应报文的封装  直接响应
                }
                //9、解析成功(token没有被篡改)：放行请求
                //为了方便后面使用userId  可以将userId存到request的头中
                String userId = map.get("userId").toString();
                request.mutate().header("userId" , userId).build();//重新构建请求报文对象
                exchange.mutate().request(request).build();//重新构建交换机对象
                return chain.filter(exchange);
            } catch (Exception e) {
                e.printStackTrace();
                //8、解析失败：跳转到登录页面
                response.setStatusCode(HttpStatus.SEE_OTHER);//设置重定向状态码
                response.getHeaders().setLocation(URI.create(jwtProperties.getRediectUrl()+
                        request.getURI()));// request.getURI() 当前请求访问的地址
                return response.setComplete();//完成响应报文的封装  直接响应
            }



//            //过滤请求的业务代码
//            //参数1：exchange交换机，可以获取请求对象和响应对象
//            //请求对象
//            ServerHttpRequest request = exchange.getRequest();
//            //响应对象
//            ServerHttpResponse response = exchange.getResponse();
//            //参数2：chain filter链对象
//            //决定是否放行请求
//            System.out.println("AuthGatewayFilterFactory   过滤请求....");
//            System.out.println("网关配置的参数列表："+ config.getPaths());
        };
    }
    //给当前filter命名的： 路由使用过滤器时 可以使用此名称指定
    @Override
    public String name() {
        return "auth";
    }

    //自定义静态内部类接收路由配置的参数列表
    @Data
    public static class Config{
        //paths代表需要登录验证的路径列表，如果访问网关的请求路径 在集合中存在 那么网关需要判断请求报文中是否携带
        // GMALL-TOKEN 并且解析成功才可以放行
        private List<String> paths;
    }
}
